/*
 * IMLocalToNetworkStorageM.nc -- network storage endpoint module
 *
 * Copyright (C) Andrey Vihrov <andrey.vihrov@gmail.com>, 2010
 * Copyright (C) Askar Salimbaev <ghostinshell@gmail.com>, 2010
 *
 * This file is part of Intermote.
 *
 * Intermote is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Intermote is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Intermote.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <Intermote.h>

// TODO: Add replication
module IMLocalToNetworkStorageM {
	uses {
		interface Packet;
		interface AMPacket;
		interface AMSend as readChunkSend;
		interface Receive as readChunkReceive;
		interface AMSend as propertySetSend;
		interface Receive as propertySetReceive;
		interface AMSend as propertyGetSend;
		interface Receive as propertyGetReceive;
		interface AMSend as writeChunkSend;
		interface Receive as writeChunkReceive;
		interface AMSend as allocateSend;
		interface Receive as allocateReceive;
		interface Receive as eraseReceive;
		interface Receive as replicatedIdReceive;
		interface IMBlockStorage as LocalStorage;
		interface Queue<queued_request_t>;
		interface IMBlockProperty as NextBlock;
		// Flooding
		interface IMFloodingAddress as FloodingAddress;
		interface Intercept as allocateFloodIntercept;
	}
}
implementation {
	static message_t reply;
	struct im_chunk readBuffer;
	queued_request_t currentReq;
	enum {
		RQ_READ,
		RQ_WRITE,
		RQ_GETNEXT,
		RQ_SETNEXT,
		RQ_ALLOCATE,
		RQ_ERASEBLOCK,
		RQ_DROP,
		RQ_FLOODALLOCATE,
	};
	task void processQueue() {

		error_t res;

		if(!(call Queue.empty())) {
			currentReq = call Queue.dequeue();
			switch(currentReq.req) {
				case RQ_READ:
					res = call LocalStorage.read(currentReq.block, currentReq.offset, &readBuffer);
					break;
				case RQ_ALLOCATE:
					res = call LocalStorage.allocate();
					break;
				case RQ_WRITE:
					res = call LocalStorage.write(currentReq.block, currentReq.offset, &currentReq.chunk);
					break;
				case RQ_GETNEXT:
					res = call NextBlock.get(currentReq.block);
					break;
				case RQ_SETNEXT:
					res = call NextBlock.set(currentReq.block, currentReq.offset);
					break;
				case RQ_ERASEBLOCK:
					res = call LocalStorage.erase(currentReq.block);
					break;
				case RQ_FLOODALLOCATE:
					res = call LocalStorage.allocate();
					break;
				default:
					res=FAIL;
			}
			if(res != SUCCESS) {
				// Continue processing
				post processQueue();
			}
		}
	}

	void checkSendStatus(error_t status) {
		if(status != SUCCESS) {
			// If send is not successful, continue serving other requests
			// TODO: Maybe repeat sending?
			post processQueue();
		}
	}

	event message_t *readChunkReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		if(len == sizeof(read_request_t)) {
			queued_request_t newReq;
			read_request_t* request = (read_request_t*)payload;
			newReq.req = RQ_READ;
			newReq.block = request->block;
			newReq.offset = request->offset;
			newReq.source = call AMPacket.source(msg);
			call Queue.enqueue(newReq);
			post processQueue();
		}

		return msg;
	}

	event void readChunkSend.sendDone (message_t *msg, error_t err)
	{
		if (msg == &reply)
			post processQueue();
	}

	event message_t *writeChunkReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		if (len == sizeof(write_request_t)) {
			queued_request_t newReq;
			write_request_t* request = (write_request_t*)payload;
			newReq.req = RQ_WRITE;
			newReq.block = request->block;
			newReq.offset = request->offset;
			newReq.source = call AMPacket.source(msg);
			memcpy(newReq.chunk.data, request->data, IM_CHUNK_SIZE);
			call Queue.enqueue(newReq);
			post processQueue();
		}
		return msg;
	}

	event void writeChunkSend.sendDone (message_t *msg, error_t err)
	{
		if (msg == &reply)
			post processQueue();
	}

	event message_t *propertyGetReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		if (len == sizeof(get_property_request_t)) {
			queued_request_t newReq;
			get_property_request_t* request = (get_property_request_t*)payload;

			if(request->property == IM_PROP_NEXT_ID) {
				newReq.req = RQ_GETNEXT;
			} else {
				return msg; //TODO: implement other requests
			}
			newReq.block = request->block;
			newReq.source = call AMPacket.source(msg);
			call Queue.enqueue(newReq);
			post processQueue();
		}

		return msg;
	}

	event void propertyGetSend.sendDone (message_t *msg, error_t err)
	{
		if (msg == &reply)
			post processQueue();
	}

	event message_t *propertySetReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		if (len == sizeof(set_property_request_t)) {
			queued_request_t newReq;
			set_property_request_t* request = (set_property_request_t*)payload;
			if(request->property == IM_PROP_NEXT_ID) {
				newReq.req = RQ_SETNEXT;
			} else {
				return msg; //TODO: implement other requests
			}
			newReq.block = request->block;
			newReq.offset = request->data;
			newReq.source = call AMPacket.source(msg);
			call Queue.enqueue(newReq);
			post processQueue();
		}
		return msg;
	}

	event void propertySetSend.sendDone (message_t *msg, error_t err)
	{
		if (msg == &reply)
			post processQueue();
	}

	event message_t *allocateReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		queued_request_t newReq;

		newReq.req = RQ_ALLOCATE;
		newReq.source = call AMPacket.source(msg);
		call Queue.enqueue(newReq);
		post processQueue();
		return msg;
	}

	event void allocateSend.sendDone (message_t *msg, error_t err)
	{
		if (msg == &reply)
			post processQueue();
	}

	event message_t *eraseReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		if (len == sizeof(erase_request_t)) {
			queued_request_t newReq;
			erase_request_t* request = (erase_request_t*)payload;
			newReq.req = RQ_ERASEBLOCK;
			newReq.block = request->block;
			newReq.source = call AMPacket.source(msg);
			call Queue.enqueue(newReq);
			post processQueue();
		}
		return msg;
	}

	event message_t *replicatedIdReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		return msg;
	}

	event bool allocateFloodIntercept.forward(message_t *msg, void *payload, uint8_t len) {
		queued_request_t newReq;

		newReq.req = RQ_FLOODALLOCATE;
		newReq.source = call FloodingAddress.address(msg);
		call Queue.enqueue(newReq);
		post processQueue();
		// TODO: consider returning FAIL, trying to allocate. Then, if allocation not successful re-flood message.
		return FALSE;
	}

	/* Completion events */

	event void LocalStorage.allocateDone (error_t err, im_block_id_t block) {

		allocate_reply_t *data;
		// TODO: No error handling?
		if(currentReq.req == RQ_FLOODALLOCATE && err != SUCCESS) {
			post processQueue();
			return;
		}
		call Packet.clear(&reply);
		data = call Packet.getPayload(&reply, sizeof(allocate_reply_t));
		data->status = err;
		data->block = block;

		checkSendStatus(call allocateSend.send(currentReq.source, &reply, sizeof(allocate_reply_t)));
	}

	event void LocalStorage.readDone (error_t err) {
		// TODO: No error handling?
		read_reply_t* data;
		if(err==SUCCESS) {
		call Packet.clear(&reply);
			data = call Packet.getPayload(&reply, IM_CHUNK_SIZE);
			memcpy(data->data,readBuffer.data, IM_CHUNK_SIZE);
			checkSendStatus(call readChunkSend.send(currentReq.source, &reply, IM_CHUNK_SIZE));
		}
	}

	event void LocalStorage.eraseDone (error_t err) {
		// TODO: No error handling?
		post processQueue();
	}

	event void LocalStorage.writeDone (error_t err) {
		call Packet.clear(&reply);
		call Packet.setPayloadLength(&reply, 0);
		checkSendStatus(call writeChunkSend.send(currentReq.source, &reply, 0));
	}

	event void NextBlock.setDone (error_t err) {
		call Packet.clear(&reply);
		call Packet.setPayloadLength(&reply, 0);
		checkSendStatus(call propertySetSend.send(currentReq.source, &reply, 0));
	}

	event void NextBlock.getDone (error_t err, uint32_t block) {
		get_property_reply_t *data;

		call Packet.clear(&reply);
		data = call Packet.getPayload(&reply, sizeof(get_property_reply_t));
		data->data = block;
		checkSendStatus(call propertyGetSend.send(currentReq.source, &reply, sizeof(get_property_reply_t)));
	}
}
